"""认证与授权相关工具

职责：
- 密码哈希与校验（使用 passlib+bcrypt）
- JWT 访问令牌的创建与解析（使用 PyJWT）
- FastAPI 依赖：获取当前用户、当前活跃用户、管理员用户
- Scope 权限校验工具：`require_scopes`，用于保护特定接口
"""

from datetime import datetime, timedelta, timezone
from typing import Annotated, Optional

import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
from passlib.context import CryptContext
from sqlmodel import Session, select

from app.core.config import settings
from app.core.db import get_session
from app.module.users.model import User, UserRole
from app.core.schemas import TokenPayload


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2 密码模式，声明获取 token 的路由路径
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证明文密码是否匹配已存储的哈希密码"""
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    """对明文密码进行哈希处理（bcrypt）"""
    return pwd_context.hash(password)


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """创建 JWT 访问令牌

    参数：
    - data: 将被编码进 token 的负载（例如 sub、scopes）
    - expires_delta: 过期时间增量；若不提供则使用默认配置
    """
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt


def decode_token(token: str) -> TokenPayload:
    """解析并验证 JWT，返回标准化的负载数据（含 scopes）"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        scopes = payload.get("scopes") or []
        if isinstance(scopes, str):
            scopes = scopes.split()
        return TokenPayload(sub=payload.get("sub"), scopes=scopes)
    except (ExpiredSignatureError, InvalidTokenError) as exc:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
            headers={"WWW-Authenticate": "Bearer"},
        ) from exc


def get_current_user(
    token: Annotated[str, Depends(oauth2_scheme)],
    session: Session = Depends(get_session),
) -> User:
    """从 Authorization: Bearer <token> 中解析并返回当前用户"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    payload = decode_token(token)
    if not payload.sub:
        raise credentials_exception

    user = session.exec(select(User).where(User.username == payload.sub)).first()
    if user is None:
        raise credentials_exception
    return user


def get_current_active_user(current_user: Annotated[User, Depends(get_current_user)]) -> User:
    """要求用户处于启用状态"""
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user")
    return current_user


def get_current_admin(current_user: Annotated[User, Depends(get_current_active_user)]) -> User:
    """要求当前用户具备管理员角色"""
    if getattr(current_user, "role", None) != UserRole.admin:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin required")
    return current_user


def require_scopes(required: list[str]):
    """生成一个依赖，用于校验访问者是否具备给定 scopes

    用法：
    - 在路由依赖中 `Depends(require_scopes(["admin:read"]))`
    - 当权限不足时，返回 403 并包含友好提示与缺失的权限列表
    """
    def dependency(token: Annotated[str, Depends(oauth2_scheme)]) -> list[str]:
        payload = decode_token(token)
        user_scopes = set(payload.scopes)
        missing = [s for s in required if s not in user_scopes]
        if missing:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail={
                    "message": "权限不足，缺少以下权限",
                    "missing_scopes": missing,
                },
                headers={"WWW-Authenticate": f"Bearer scope=" + " ".join(required)},
            )
        return payload.scopes

    return dependency


